package org.icosagon.core.character;

/* Calculating XP to level.
 * E(x) = 500x^2 + 500x, where x = the level, and E(x) = the experience required
 * to reach level x. (NOTE: x^2 = x squared).
 */

import org.icosagon.core.Definable;
import org.icosagon.core.character.traits.Alignment;
import org.icosagon.core.character.traits.Feat;
import org.icosagon.core.items.Item;
import org.icosagon.core.items.Equipment;
import java.util.Vector;
import org.icosagon.core.InvalidNumberException;
import org.icosagon.core.character.traits.Ability;

/**
 * The class used to store all information about a players character.
 */
public class Character extends Definable {
    //The race of the character
    private Race race;
    //Holds all of the classes that this character has levels in.
    private Vector<Class> classes = new Vector<Class>();
    //Languages that the character has personally learned.
    private Vector<String> languages = new Vector<String>();
    //Feats that the character has learned.
    private Vector<Feat> feats = new Vector<Feat>();
    //Items that the character is holding.
    private Vector<Item> items = new Vector<Item>();
    //Things that this character is wearing/using.
    private Vector<Equipment> equipment = new Vector<Equipment>();
    //Abilities that the character poseses
    private Vector<Ability> abilities = new Vector<Ability>();
    
    private double height;
    private double weight;
    private String gender = null;
    private String deity = null;
    private Alignment alignment = null;
    private int hp = 0;
    private int capacity = 0;
    private int expereince = 0;

    private Character() {}
    public Character(String name, Race race, Class cl) {
        this.setName(name);
        this.race = race;
        this.addClass(cl);
    }
    
    /**
     * Sums up the level of all of the classes belonging to this character and returns them.
     * @return The player level.
     */
    public int getLevel() {
        int temp = 0;
        for(Class cl : this.classes)
            temp += cl.getLevel();
        return temp;
    }
    
    /**
     * Returns the Race of the character.
     * @return Character's Race.
     */
    public Race getRace() {
        return this.race;
    }
    
    /**
     * A character can have only one race.
     * @param race
     */
    public void setRace(Race race) {
        this.race = race;
    }
    
    /**
     * Adds a language that this character can speak.
     * @param language
     */
    public void addLanguage(String language) { //Add Inteligence check.
        language = language.trim();
        if(!language.equals("") || language != null)
            this.languages.add(language.trim());
    }
    
    public boolean removeLanguage(String language) {
        language = language.trim();
        if(!language.equals("") || language != null) {
            for(String token : this.languages) {
                if(token.equalsIgnoreCase(language)) {
                    this.languages.remove(language);
                    return true;
                }
            }
        }
        return false;
    }
    
    /**
     * Returns all of the character's known languages in a <code>Vector</code>.
     * @return Known languages.
     */
    public Vector<String> getLanguages() {
        return this.languages;
    }

    public void setExperience(int exp) {
        this.expereince = exp;
    }

    public void addExperience(int exp) {
        this.expereince += exp;
    }
    
    public void removeExperience(int exp) {
        this.expereince -= exp;
    }
    
    public int getExperience() {
        return this.expereince;
    }
    
    public void setCapacity(int value) {
        this.capacity = value;
    }
    
    public int getCapacity() {
        return this.capacity;
    }
    
    public void addItem(Item item) {
        this.items.add(item);
    }
    
    public void removeItem(Item item) {
        this.items.remove(item);
    }
    
    public Vector<Item> getItems() {
        return this.items;
    }
    
    public void equipItem(Equipment equipment) {
        this.equipment.add(equipment);
    }
    
    public void removeEquipment(Equipment equipment) {
        this.equipment.remove(equipment);
    }
    
    public Vector<Equipment> getEquipment() {
        return this.equipment;
    }
    
    /**
     * Adds the base hit points to any effects from class, race or equipment.
     * @return
     */
    public int getHitPoints() {
        return this.hp;
    }
    
    /**
     * Sets the amount of damage a character can take.
     * @param points
     */
    public void setHitPoints(int points) {
        this.hp = points;
    }
    
    /**
     * Adds the points passed to the total number of hit points.
     * @param points
     */
    public void addHitPoints(int points) {
        this.hp += points;
    }
    
    /**
     * Returns all of the characters classes.
     * @return
     */
    public Vector<Class> getClasses() {
        return this.classes;
    }
    
    /**
     * Adds another Class to a character. If the character already has a class
     * with that name a MultipleClassException is thrown.
     * @param cl Class to be added.
     * @throws opencharacter.MultipleClassException
     */
    public void addClass(Class cl) {
        for(Class temp : this.classes) {
            if(cl.equals(temp))
                throw new MultipleClassException("This character already has a class of this name.");
        }
        this.classes.add(cl);
    }
    
    /**
     * Removes a class from the list by name.
     * @param classname
     * @return
     */
    public boolean removeClass(String classname) {
        for(Class temp : this.classes) {
            String name = temp.getName();
            if(classname.equalsIgnoreCase(name)) {
                this.classes.remove(temp);
                return true;
            }
        }
        return false;
    }
    
    public void addFeat(Feat feat) {
        this.feats.add(feat);
    }
    
    public void removeFeat(Feat feat) {
        this.feats.remove(feat);
    }
    
    public Vector<Feat> getFeats() {
        return this.feats;
    }
    
    /**
     * Sets the height of your character. If you try to select a height out of
     * the range of your race's height constraints an error will be thrown.
     * @param height
     */
    public void setHeight(double height) {
        if(height < 0)
            throw new InvalidNumberException("Value can not be negative.");
        this.height = height;
    }
    
    public void setWeight(double weight) {
        if(weight < 0)
            throw new InvalidNumberException("Value can not be negative.");
        this.weight = weight;
    }
    
    public double getHeight() {
        return this.height;
    }

    public double getWeight() {
        return this.weight;
    }
    
    public void setGender(String gen) {
        this.gender = gen.trim();
    }
    
    public String getGender() {
        return this.gender;
    }
    
    public void setDeity(String name) {
        this.deity = name.trim();
    }
    
    public String getDeity() {
        return this.deity;
    }
    
    public void setAlignment(Alignment align) {
        this.alignment = align;
    }
    
    public Alignment getAlignment() {
        return this.alignment;
    }
    
    public void setAbility(Ability ability, int value) {
        for(Ability temp : this.abilities) {
            if(temp.getName().toLowerCase().equals(ability.getName().toLowerCase())) {
                temp.setValue(value);
                return;
            }
        }
        ability.setValue(value);
        this.abilities.add(ability);
    }

    /**
     * Returns the value of the ability type passed to this method.
     * @param ability The comparative object type.
     * @return The ability score of the passed ability type.
     */
    public int getAbility(Ability ability) {
        for(Ability temp : this.abilities)
            if(temp.getName().equals(ability.getName()))
                return temp.getValue();
        return -1;
    }

    /**
     *  Returns the value of the named ability type.
     * @param ability The name of the ability value you want to retrieve.
     * @return The ability score of the passed name.
     */
    public int getAbility(String ability) {
        for(Ability temp : this.abilities)
            if(temp.getName().toLowerCase().equals(ability.toLowerCase()))
                return temp.getValue();
        return -1;
    }

    public Vector<Ability> getAbilities() {
        return this.abilities;
    }
}